package com.taxi.comm.server;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Iterator;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
 * A singleton instance hold the configuration from pmsfront.xml
 * 
 * @author Watertao
 *
 */
public class Configuration {

	private static Configuration config;
	
	private int port;
	
	private int processorCnt;
	
	private int executorCnt;
	
	private int backlog;
	
	private String encoding;
	
	private Session session;
	
	private String oprCode;
	
	private int compressLimit;
	
	private String patchDir;
	
	private String patchUrlPrefix;
	
	public String getPatchDir() {
		return patchDir;
	}

	public String getPatchUrlPrefix() {
		return patchUrlPrefix;
	}

	public int getCompressLimit() {
		return compressLimit;
	}

	public String getOprCode() {
		return oprCode;
	}

	public String getEncoding() {
		return encoding;
	}

	public int getPort() {
		return port;
	}

	public int getProcessorCnt() {
		return processorCnt;
	}

	public int getExecutorCnt() {
		return executorCnt;
	}

	public int getBacklog() {
		return backlog;
	}

	public Session getSession() {
		return session;
	}

	public static Configuration getInstance() throws IllegalStateException {
		if (config == null) {
			throw new IllegalStateException("Configuration not initialized");
		}
		
		return config;
	}
	
	public static void initConfig(String configPath) throws Exception {
		config = new Configuration(configPath);
	}
	
	public static void initConfig(InputStream configPathIn) throws Exception {
		config = new Configuration(configPathIn);
	}
	
	private void validateConfigFile(Document doc) throws SAXException,
			IOException {
		SchemaFactory sfactory = SchemaFactory
				.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

		StreamSource ss = new StreamSource(
				Configuration.class.getResourceAsStream("/pmsfront.xsd"));
		Schema schema = sfactory.newSchema(ss);
		Validator validator = schema.newValidator();

		validator.validate(new DOMSource(doc));
	}

	private Document parseConfigFile(InputStream inStream) throws SAXException,
			IOException, ParserConfigurationException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true); // never forget this!
		DocumentBuilder builder = factory.newDocumentBuilder();
		Document doc = builder.parse(inStream);
		return doc;
	}

	private XPath getXpath() {
		XPathFactory xfactory = XPathFactory.newInstance();
		XPath xpath = xfactory.newXPath();
		xpath.setNamespaceContext(new NamespaceContext() {
			@Override
			public Iterator getPrefixes(String namespaceURI) {
				throw new UnsupportedOperationException();
			}

			@Override
			public String getPrefix(String namespaceURI) {
				throw new UnsupportedOperationException();
			}

			@Override
			public String getNamespaceURI(String prefix) {
				if (prefix == null)
					throw new NullPointerException("Null prefix");
				else if ("n".equals(prefix))
					return "http://www.huateng.com/pmsfront";
				else if ("xml".equals(prefix))
					return XMLConstants.XML_NS_URI;
				return XMLConstants.NULL_NS_URI;
			}
		});
		return xpath;
	}
	
	private Session wrapSessionConfig(XPath xpath, Document doc) throws XPathExpressionException {
		
		int readIdle = Integer.parseInt((String) xpath.evaluate("//n:Session/n:ReadIdle/text()", doc, XPathConstants.STRING));
		
		int writeIdle = Integer.parseInt((String) xpath.evaluate("//n:Session/n:WriteIdle/text()", doc, XPathConstants.STRING));
		
		int receiveBufferSize = Integer.parseInt((String) xpath.evaluate("//n:Session/n:ReceiveBufferSize/text()", doc, XPathConstants.STRING));
		
		Session session = new Session(readIdle, writeIdle, receiveBufferSize);
		
		return session;
	}
	
	
	private Configuration(String configPath) throws Exception {

		this(new FileInputStream(configPath));
			
	}
	
	private Configuration(InputStream inStream) throws Exception {
		// parse the configuration file
		Document doc = parseConfigFile(inStream);
		// validate the config file against the schema
		validateConfigFile(doc);
		// get the xpath instance
		XPath xpath = getXpath();
		
		
		// start to construct the configuration object
		
		// Encoding
		String encoding = (String) xpath.evaluate("//n:Encoding/text()", doc, XPathConstants.STRING);
		Charset.forName(encoding);
		this.encoding = encoding;
		
		this.port = Integer.parseInt((String) xpath.evaluate("//n:Port/text()", doc, XPathConstants.STRING));
		
		this.processorCnt = Integer.parseInt((String) xpath.evaluate("//n:ProcessorCount/text()", doc, XPathConstants.STRING));
		
		this.executorCnt = Integer.parseInt((String) xpath.evaluate("//n:ExecutorCount/text()", doc, XPathConstants.STRING));
		
		this.backlog = Integer.parseInt((String) xpath.evaluate("//n:Backlog/text()", doc, XPathConstants.STRING));
		
		this.session = wrapSessionConfig(xpath, doc);
		
		this.oprCode = (String) xpath.evaluate("//n:Business/n:OprCode/text()", doc, XPathConstants.STRING);
		
		this.compressLimit = Integer.parseInt((String) xpath.evaluate("//n:Business/n:CompressLimit/text()", doc, XPathConstants.STRING));
		
		this.patchDir = (String) xpath.evaluate("//n:Business/n:PatchDir/text()", doc, XPathConstants.STRING);
		
		this.patchUrlPrefix = (String) xpath.evaluate("//n:Business/n:PatchURLPrefix/text()", doc, XPathConstants.STRING);
		
	}
	
}
